using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Build.Utilities;
using Microsoft.Build.Framework;
using Microsoft.BizTalk.ExplorerOM;
using Microsoft.BizTalk.ApplicationDeployment;
using System.Reflection;
using System.IO;
using System.Xml;
using System.Text.RegularExpressions;


namespace BizTalk.BuildGenerator.Tasks
{
    /// <summary>
    /// This class will inspect a BizTalk application and document its assembly dependancies
    /// </summary>
    public class BizTalkAssemblyDependancyDocumenter : Task
    {
        private string _ApplicationName;
        private string _ManagementDatabaseName = "BizTalkMgmtDb";
        private string _ServerName = ".";
        private StringBuilder _Builder = new StringBuilder();
        private string _OutputPath;
        private string _ExcludeExpression;
                             
        /// <summary>
        /// Overrides the Execute method
        /// </summary>
        /// <returns></returns>
        public override bool Execute()
        {
            using (FileStream fs = new FileStream(this._OutputPath, FileMode.Create, FileAccess.Write))
            {
                XmlTextWriter writer = new XmlTextWriter(new StreamWriter(fs));

                writer.WriteStartDocument();
                writer.WriteStartElement("BizTalkApplicationDependancyMatrix");
                writer.WriteStartElement("Application");
                writer.WriteAttributeString("Name", this._ApplicationName);

                using (Microsoft.BizTalk.ApplicationDeployment.Group group = new Microsoft.BizTalk.ApplicationDeployment.Group())
                {
                    group.DBName = this._ManagementDatabaseName;
                    group.DBServer = this._ServerName;
                    Microsoft.BizTalk.ApplicationDeployment.Application app = group.Applications[this._ApplicationName];
                    if (app == null)
                        throw new ApplicationException("Invalid application name");

                    foreach (Resource resource in app.ResourceCollection)
                    {
                        string resourceType = GetResourceType(resource.ResourceType);
                        if (resourceType == ResourceTypes.Assembly
                            || resourceType == ResourceTypes.BizTalkAssembly)
                        {
                            writer.WriteStartElement("Assembly");
                            writer.WriteAttributeString("FullName", resource.Luid);
                            AddResourceReferences(resource, writer);
                            writer.WriteEndElement();
                        }
                    }
                }

                writer.WriteEndElement();
                writer.WriteEndElement();
                writer.Flush();
            }

            return true;
        }

        /// <summary>
        /// Returns just the stuff after the : in the type name
        /// </summary>
        /// <param name="typeDescription"></param>
        /// <returns></returns>
        private static string GetResourceType(string typeDescription)
        {
            int splitPosition = typeDescription.IndexOf(":");
            return typeDescription.Substring(splitPosition + 1);
        }

        /// <summary>
        /// Uses reflection to load the references of each application resource
        /// </summary>
        /// <param name="resource"></param>
        /// <param name="writer"></param>
        private void AddResourceReferences(Resource resource, XmlTextWriter writer)
        {
            const string SourceLocationPropertyName = "SourceLocation";

            writer.WriteStartElement("Dependancies");
            string location = (string)resource.Properties[SourceLocationPropertyName];
            Assembly a = Assembly.LoadFile(location);
            foreach (AssemblyName name in a.GetReferencedAssemblies())
            {
                bool excludeAssembly = true;

                if (!string.IsNullOrEmpty(this._ExcludeExpression))                
                    excludeAssembly = Regex.IsMatch(name.FullName, this._ExcludeExpression, RegexOptions.IgnoreCase);                

                if (!excludeAssembly)
                {
                    writer.WriteStartElement("Dependancy");
                    writer.WriteAttributeString("AssemblyName", name.Name);
                    writer.WriteAttributeString("AssemblyVersion", name.Version.ToString());
                    writer.WriteAttributeString("AssemblyFullName", name.FullName);
                    writer.WriteEndElement();
                }
                else
                    Logger.LogMessage(this, "The assembly " + name.FullName + " has been excluded as it meets the exclude expression");
            }
            writer.WriteEndElement();       
        }

        #region Properties
        /// <summary>
        /// An expression to exclude assemblies where the name matches a certail regular expression
        /// </summary>
        public string ExcludeExpression
        {
            get { return _ExcludeExpression; }
            set { _ExcludeExpression = value; }
        }
        /// <summary>
        /// The output path where the file is created
        /// </summary>
        [Required]
        public string OutputPath
        {
            get { return _OutputPath; }
            set { _OutputPath = value; }
        }
        /// <summary>
        /// The name of the BizTalk application
        /// </summary>
        [Required]
        public string ApplicationName
        {
            get { return _ApplicationName; }
            set { _ApplicationName = value; }
        }
        /// <summary>
        /// Optional name of the server
        /// </summary>
        public string ServerName
        {
            get { return _ServerName; }
            set { _ServerName = value; }
        }
        /// <summary>
        /// Optional name for the managemetn db
        /// </summary>
        public string ManagementDatabaseName
        {
            get { return _ManagementDatabaseName; }
            set { _ManagementDatabaseName = value; }
        }
        #endregion
    }

    internal class ResourceTypes
    {
        public const string File = "File";
        public const string PreProcessingScript = "PreProcessingScript";
        public const string Assembly = "Assembly";
        public const string Com = "Com";
        public const string BizTalkAssembly = "BizTalkAssembly";
        public const string Bam = "Bam";
        public const string BizTalkBinding = "BizTalkBinding";
        public const string PostProcessingScript = "PostProcessingScript";
        public const string WebDirectory = "WebDirectory";
    }
}
